package LatentTicTacToe.Calculators;

import java.util.LinkedList;

import LatentTicTacToe.Data.Move;
import LatentTicTacToe.InformationSets.CFRInformationSet;

public class LTTTValueCalculator extends ValueCalculator{

	public LTTTValueCalculator() {
	}
	@Override
	public void updateValues(int iteration) {
		updateValues("", null);
	}
	
	/**
	 * Updates values of available moves in every information set
	 * 
	 * @param id
	 * @return
	 */
	private float[] updateValues(String id, CFRInformationSet parent) {

		CFRInformationSet set = gameTree.get(id);

		if (id == "") {
			set.values = updateValues("0", set);
			return null;
		}

		if (set == null) {
			return null;
		}

		if (set.winner != 0) {
			if (set.winner == 1) {
				return new float[] { 1 };
			} else if (set.winner == 2) {
				return new float[] { -1 };
			} else {
				return new float[] { 0 };
			}
		}

		LinkedList<float[]> valuesFromLowerLevel = getValuesFromLowerLevel(id, set);
		float[] valuesToPropagateUp = getValuesToPropagateUp(id, parent, set, valuesFromLowerLevel);

		updateSingleSet(id, parent, set, valuesFromLowerLevel);
		updateSet(parent, set, valuesFromLowerLevel);

		return valuesToPropagateUp;
	}

	private void updateSet(CFRInformationSet parent, CFRInformationSet set, LinkedList<float[]> valuesFromLowerLevel) {
		
		int counter = 0;

		for (float[] values : valuesFromLowerLevel) {

			if (set.values.length == values.length + 1) {
				for (int j = 0; j < values.length; j++) {
					if (j < counter) {

						set.values[j] += parent.getStrategyForMove(set.moves.get(counter)) * values[j];
					} else {

						set.values[j + 1] += parent.getStrategyForMove(set.moves.get(counter)) * values[j];
					}
				}
			}

			counter++;

		}

		replaceParentStrategy(set, valuesFromLowerLevel, parent.moves.size());
	}

	private void updateSingleSet(String id, CFRInformationSet parent, CFRInformationSet set, LinkedList<float[]> valuesFromLowerLevel) {
		
		set.clearValues();
		int counter = 0;

		for (Move possibleMove : set.moves) {

			String singleSetId = addMoveToId(addMoveToId(id, possibleMove.toString()), possibleMove.toString());
			CFRInformationSet singleSet = gameTree.get(singleSetId);

			if (singleSet == null) {
				continue;
			}

			float[] tempValues = valuesFromLowerLevel.get(counter);


			for (int i = 0; i < tempValues.length; i++) {

				singleSet.values[i] = tempValues[i];
				set.values[counter] += parent.getStrategyForMove(possibleMove) * set.getStrategyForMove(possibleMove) * singleSet.getStrategy()[i] * tempValues[i];

			}

			counter++;
		}
	}

	private LinkedList<float[]> getValuesFromLowerLevel(String id, CFRInformationSet set) {
		
		LinkedList<float[]> valuesFromLowerLevel = new LinkedList<float[]>();

		for (Move possibleMove : set.moves) {

			String idWithPossibleMove = addMoveToId(id, possibleMove.toString());

			float[] tempValues = updateValues(idWithPossibleMove, set);

			if (tempValues == null) {

				int counter1 = 0;

				tempValues = new float[set.moves.size() - 1];

				for (Move move : set.moves) {

					if (!move.equals(possibleMove)) {
						tempValues[counter1++] = updateValues(addMoveToId(idWithPossibleMove, move.toString()), null)[0];
					}

				}

			}

			valuesFromLowerLevel.add(tempValues.clone());

		}
		
		return valuesFromLowerLevel;
	}

	private float[] getValuesToPropagateUp(String id, CFRInformationSet parent, CFRInformationSet set, LinkedList<float[]> valuesFromLowerLevel) {
		
		float[] valuesToPropagateUp = new float[set.moves.size()];
		int counter = 0;

		for (float[] tempValues : valuesFromLowerLevel) {
			
			addValues(valuesToPropagateUp, getValuesFromSet(set, counter, tempValues));

			addValues(valuesToPropagateUp, getValuesFromSingleSet(id, parent, set, counter, tempValues));

			counter++;

		}
		return valuesToPropagateUp;
	}


	private void addValues(float[] valuesToPropagateUp, float[] valuesFromSet) {
		for (int i = 0; i < valuesToPropagateUp.length; i++) {
			valuesToPropagateUp[i] += valuesFromSet[i];
		}
		
	}

	private float[] getValuesFromSingleSet(String id, CFRInformationSet parent, CFRInformationSet set,  int counter, float[] tempValues) {
		
		Move possibleMove = set.moves.get(counter);
		float[] valuesToPropagateUp = new float[set.moves.size()];

		String singleSetId = addMoveToId(addMoveToId(id, possibleMove.toString()), possibleMove.toString());
		CFRInformationSet singleSet = gameTree.get(singleSetId);

		if (singleSet != null) {
			for (int i = 0; i < tempValues.length; i++) {
				valuesToPropagateUp[counter] += parent.getStrategyForMove(possibleMove) * set.getStrategyForMove(possibleMove) * singleSet.getStrategy()[i] * tempValues[i];
			}
		}
		
		return valuesToPropagateUp;
	}

	private float[] getValuesFromSet(CFRInformationSet set, int counter, float[] tempValues) {
		
		float[] valuesToPropagateUp = new float[set.moves.size()];
		
		if (tempValues.length == 1) {
			valuesToPropagateUp[counter] += tempValues[0];
		} else {

			for (int i = 0; i < tempValues.length; i++) {
				if (i < counter) {
					valuesToPropagateUp[counter] += set.getStrategy()[i] * tempValues[i];
				}
				if (i >= counter) {
					valuesToPropagateUp[counter] += set.getStrategy()[i + 1] * tempValues[i];
				}
			}
		}
		
		return valuesToPropagateUp;
	}

	private void replaceParentStrategy(CFRInformationSet set, LinkedList<float[]> valuesFromLowerLevel, int parentMoveCount) {

		for (int i = 0; i < set.values.length; i++) {
			if (set.values[i] != 0) {
				return;
			}
		}

		int counter = 0;

		for (float[] values : valuesFromLowerLevel) {

			if (set.values.length == values.length + 1) {
				for (int j = 0; j < values.length; j++) {
					if (j < counter) {
						set.values[j] += 1. / parentMoveCount * values[j];
					} else {
						set.values[j + 1] += 1. / parentMoveCount * values[j];
					}
				}
			}

			counter++;

		}

	}

	private String addMoveToId(String id, String move) {

		StringBuilder builder = new StringBuilder();

		builder.append(id);
		builder.append(".");
		builder.append(move);

		return builder.toString();
	}

}
